import type { Directive, DirectiveBinding, App } from "vue";
import { createApp } from "vue";
import { Spin } from "@arco-design/web-vue";

interface SpinInstance {
  spinApp: App;
  spinContainer: HTMLElement;
}

interface HTMLElementWithSpin extends HTMLElement {
  __spinInstance?: SpinInstance;
}

interface SpinBindingValue {
  spinning: boolean;
  tip: string;
}

type paramsType = (binding: DirectiveBinding<boolean | SpinBindingValue>) => {
  spinning: boolean;
  tip: string;
};
const getParams: paramsType = binding => {
  if (typeof binding.value === "boolean") {
    return {
      spinning: binding.value,
      tip: "加载中..."
    };
  }
  return {
    spinning: binding.value?.spinning || false,
    tip: binding.value?.tip || "加载中..."
  };
};
const createSpinContainer = (spinApp: App, spinning: boolean) => {
  const spinContainer = document.createElement("div");
  spinApp.mount(spinContainer);
  spinContainer.style.position = "absolute";
  spinContainer.style.top = "0";
  spinContainer.style.left = "0";
  spinContainer.style.width = "100%";
  spinContainer.style.height = "100%";
  spinContainer.style.display = spinning ? "flex" : "none";
  spinContainer.style.justifyContent = "center";
  spinContainer.style.alignItems = "center";
  spinContainer.style.backgroundColor = "rgba(255, 255, 255, 0.8)";
  spinContainer.style.zIndex = "999";
  return spinContainer;
};

const SpinDirective: Directive = {
  mounted(el: HTMLElementWithSpin, binding: DirectiveBinding<boolean | SpinBindingValue>) {
    const { spinning, tip } = getParams(binding);
    const spinApp = createApp(Spin, {
      spinning,
      size: "large",
      tip
    });
    const spinContainer = createSpinContainer(spinApp, spinning);
    const computedStyle = getComputedStyle(el);
    if (computedStyle.position === "static" || !computedStyle.position) {
      el.style.position = "relative";
    }
    el.appendChild(spinContainer);
    el.__spinInstance = {
      spinApp,
      spinContainer
    };
  },

  updated(el: HTMLElementWithSpin, binding: DirectiveBinding<boolean | SpinBindingValue>) {
    if (el.__spinInstance) {
      const { spinContainer } = el.__spinInstance;
      const { spinning, tip } = getParams(binding);
      spinContainer.style.display = spinning ? "flex" : "none";
      const { spinApp } = el.__spinInstance;
      const instance = spinApp._instance as any;
      instance.props.spinning = spinning;
      instance.props.tip = tip;
    }
  },

  unmounted(el: HTMLElementWithSpin) {
    if (el.__spinInstance) {
      const { spinApp, spinContainer } = el.__spinInstance;
      spinApp.unmount();
      spinContainer.remove();
      delete el.__spinInstance;
    }
  }
};

export default SpinDirective;
